﻿using System;
using System.Linq;

namespace Sencha.Serialization.Serializers
{
    using Contracts;
    using System.Collections.Generic;

    public abstract class DiscriminatedObjectSerializer<ItemT> : ObjectSerializer<ItemT>
    {
        private MemberDescription m_discriminatorMember;
        private string m_discriminatorValue;
        private Dictionary<string, ITypeSerializer> m_typeMapping;

        protected DiscriminatedObjectSerializer(DataContract contract, TypeDescription discriminatedType)
            : base(contract, discriminatedType)
        {
            if (discriminatedType == null)
                throw new ArgumentNullException("disriminatedType");
            System.Diagnostics.Contracts.Contract.EndContractBlock();

            var mappingCapacity = Math.Min(10, contract.GetTypeHeirs(discriminatedType).Count());
            var discrComparer = this.GetDiscriminatorComparer() ?? EqualityComparer<string>.Default;
            m_typeMapping = new Dictionary<string, ITypeSerializer>(mappingCapacity, discrComparer);
            m_discriminatorMember = this.GetDiscriminatorMember();

            if (m_discriminatorMember == null)
                throw new Exceptions.TypeContractViolation(this.GetType(), discriminatedType, "have discriminator");
        }

        /// <summary>
        /// Must return the member by which types is discriminated
        /// </summary>
        /// <param name="disriminatedType">Type to find member in</param>
        /// <returns>Member, not null</returns>
        protected abstract MemberDescription GetDiscriminatorMember();
        /// <summary>
        /// Get quality comparer for discriminator's values
        /// </summary>        
        /// <returns>Comparer, or null for default comparer</returns>
        protected virtual IEqualityComparer<string> GetDiscriminatorComparer()
        {
            return EqualityComparer<string>.Default;
        }
        /// <summary>
        /// Maps discriminator value to type
        /// </summary>
        /// <param name="discriminatorValue">value</param>
        /// <param name="typeInfo">associated type</param>
        protected void MapType(string discriminatorValue, TypeDescription typeInfo)
        {
            if (typeInfo == null)
                throw new ArgumentNullException("typeInfo");
            System.Diagnostics.Contracts.Contract.EndContractBlock();

            if (m_typeMapping.ContainsKey(discriminatorValue))
                throw new Exceptions.DiscriminatorValueAlreadyMapped(this.TypeDescription, Convert.ToString(discriminatorValue));

			m_typeMapping.Add(discriminatorValue, this.Contract.GetSerializerOfType(typeInfo));
        }

        public override ItemT Deserialize(IJsonReader reader)
        {
            var value = base.Deserialize(reader);

            // resets discriminator's value
            this.m_discriminatorValue = default(string);

            return value;
        }

        internal override ITypeSerializer GetHeirSerializer(IJsonReader reader)
        {
            var serializer = default(ITypeSerializer);
            var dicriminatorValue = m_discriminatorValue;
            if (!m_typeMapping.TryGetValue(dicriminatorValue, out serializer))
                throw new Exceptions.UnmappedDiscriminatorValue(base.TypeDescription, m_discriminatorMember, Convert.ToString(m_discriminatorValue));

            return serializer;
        }
        protected internal override void ValidateValue(IJsonReader reader, string memberName)
        {
            if (memberName == m_discriminatorMember.Name)
                m_discriminatorValue = reader.Value.AsType<string>();
            else
                base.ValidateValue(reader, memberName);
        }
    }

    /*
    Example:
    public sealed class AnimalSerializationStrategy : DiscriminatedObjectSerializationStrategy<Int32, Animal>
    {
        public AnimalSerializationStrategy(DataContract contract, TypeInfo discriminatedType)
            : base(contract, discriminatedType)
        {
            base.MapType(1, this.Contract.GetKnownType(typeof(MyApplication.Dog));
            base.MapType(2, this.Contract.GetKnownType(typeof(MyApplication.Cat));
            base.MapType(3, this.Contract.GetKnownType(typeof(MyApplication.Horse));
            base.MapType(4, this.Contract.GetKnownType(typeof(MyApplication.Goatse));
        }

        protected override MemberInfo GetDiscriminatorMember()
        {
            return base.SerializableType.Members.Find("Kind");
        }
    }
    */
}
